﻿using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using LightCAD.MathLib;

namespace LightCAD.Core.Elements
{
    public interface IElementContainerRef
    {
        public Matrix3 Matrix { get; set; }

        public ElementSpace Container { get; }
    }
    public class LcBlockRef : LcElement, IElementContainerRef
    {
        private Vector2 insertPoint;
        private Matrix3 matrixInverse;
        public string Name { get; set; }

        [JsonIgnore]
        public ElementSpace Container => this.Block;

        [JsonIgnore]
        public LcBlock Block { get; set; }

        [JsonIgnore]
        public Vector2 InsertPoint
        {
            get
            {
                if (insertPoint == null)
                {
                    insertPoint = Matrix.GetTranslateVector();
                }
                return insertPoint;
            }
        }
        public Matrix3 Matrix { get; set; } = Matrix3.Identity;

        public Matrix3 MatrixInverse
        {
            get
            {
                if (matrixInverse == null)
                {
                    matrixInverse = Matrix.Clone().Invert();
                }
                return matrixInverse;
            }
        }
        /// <summary>
        /// PolylineData
        /// </summary>
        public string ClipArea { get; set; }
        public bool IsClipOn { get; set; }

        public LcBlockRef()
        {
            this.Type = BuiltinElementType.BlockRef;
        }
        public LcBlockRef(LcBlock block) : this()
        {
            this.Block = block;
        }
        public Vector2 WcsToBlk(Vector2 point)
        {
            return MatrixInverse.MultiplyPoint(point);
        }
        public Vector2 BlkToWcs(Vector2 point)
        {
            return Matrix.MultiplyPoint(point);
        }
        public void Set(
            string name = null,
            Matrix3 matrix = null,
            string clipArea = null,
            bool? isClipOn = false,
            bool fireChangedEvent = true)
        {
            //PropertySetter:Center,Radius            

            if (!fireChangedEvent)
            {
                if (name != null) this.Name = name;
                if (matrix != null) this.Matrix = matrix;
                if (clipArea != null) this.ClipArea = clipArea;
                if (isClipOn != null) this.IsClipOn = isClipOn.Value;
            }
            else
            {

                bool chg_name = (name != null && name != this.Name);
                if (chg_name)
                {
                    OnPropertyChangedBefore(nameof(Name), this.Name, name);
                    var oldValue = this.Name;
                    this.Name = name;
                    OnPropertyChangedAfter(nameof(Name), oldValue, this.Name);
                }

                bool chg_matrix = (matrix != null && matrix != this.Matrix);
                if (chg_matrix)
                {
                    OnPropertyChangedBefore(nameof(Matrix), this.Matrix, matrix);
                    var oldValue = this.Matrix;
                    this.Matrix = matrix;
                    OnPropertyChangedAfter(nameof(Matrix), oldValue, this.Matrix);
                }
                bool chg_clipArea = (clipArea != null && clipArea != this.ClipArea);
                if (chg_clipArea)
                {
                    OnPropertyChangedBefore(nameof(ClipArea), this.ClipArea, clipArea);
                    var oldValue = this.ClipArea;
                    this.ClipArea = clipArea;
                    OnPropertyChangedAfter(nameof(ClipArea), oldValue, this.ClipArea);
                }
                bool chg_isClipOn = (isClipOn != null && isClipOn != this.IsClipOn);
                if (chg_isClipOn)
                {
                    OnPropertyChangedBefore(nameof(IsClipOn), this.IsClipOn, isClipOn);
                    var oldValue = this.IsClipOn;
                    this.IsClipOn = isClipOn.Value;
                    OnPropertyChangedAfter(nameof(IsClipOn), oldValue, this.IsClipOn);
                }

            }
        }

        public void Explode()
        {

        }
        public override LcElement Clone()
        {
            var clone = new LcBlockRef();

            clone.Copy(this);
            return clone;
        }

        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var blkref = ((LcBlockRef)src);
            this.Matrix = blkref.Matrix;
            this.Block = blkref.Block;
            this.ClipArea = blkref.ClipArea;
            this.IsClipOn = blkref.IsClipOn;
            this.Name = blkref.Name;

        }

        public override Box2 GetBoundingBox()
        {
            var box = Box2.Empty;
            foreach (var ele in this.Block.Elements)
            {
                box.Union(ele.GetBoundingBox(this.Matrix));
            }
            return box;
        }

        public override Box2 GetBoundingBox(Matrix3 matrix)
        {
            var mat = matrix * this.Matrix;
            var box = Box2.Empty;
            foreach (var ele in this.Block.Elements)
            {
                box.Union(ele.GetBoundingBox(mat));
            }
            return box;
        }
        public override bool IntersectWithBox(Polygon2d testPoly, List<RefChildElement> intersectChildren = null)
        {
            Box2 thisBox = this.BoundingBox;
            if (!thisBox.IntersectsBox(testPoly.BoundingBox))
            {
                return false;
            }

            var mtestPoly = testPoly.Multiply(this.MatrixInverse);

            foreach (var ele in this.Block.Elements)
            {
                if (intersectChildren == null)
                {
                    //如果不计算子元素相交，任意子元素相交，则本元素相交
                    if (ele.IntersectWithBox(mtestPoly))
                    {
                        return true;
                    }
                }
                else
                {
                    if (ele.IntersectWithBox(mtestPoly))
                    {
                        intersectChildren.Add(new RefChildElement(ele, this));
                    }
                }
            }
            if (intersectChildren == null) return false;
            //任意子元素被相交，则元素被相交
            return intersectChildren.Count > 0;
        }

        public override bool IncludedByBox(Polygon2d testPoly, List<RefChildElement> includedChildren = null)
        {
            Box2 thisBox = this.BoundingBox;
            if (!thisBox.IntersectsBox(testPoly.BoundingBox))
            {
                return false;
            }
            var mtestPoly = testPoly.Multiply(this.MatrixInverse);

            foreach (var ele in this.Block.Elements)
            {
                if (includedChildren == null)
                {
                    //如果不需要计算子元素包含情况，有一个子元素没包含，则本元素不被包含
                    if (!ele.IncludedByBox(mtestPoly))
                    {
                        return false;
                    }
                }
                else
                {
                    if (ele.IncludedByBox(mtestPoly))
                    {
                        includedChildren.Add(new RefChildElement(ele, this));
                    }
                }
            }
            if (includedChildren == null) return true;
            //所有子元素都被包含，则本元素被包含
            return includedChildren.Count == this.Block.Elements.Count;
        }
        public void Reset()
        {
            ResetBoundingBox();
            matrixInverse = null;
            insertPoint = null;

        }
        public override void Scale(Vector2 basePoint, double scaleFactor)
        {
            Matrix3 scaleMatrix = Matrix3.GetScale(scaleFactor, basePoint);
            this.Set(matrix: scaleMatrix * this.Matrix);
            Reset();
        }
        public override void Rotate(Vector2 basePoint, double rotateAngle)
        {
            Matrix3 rotateMatrix = Matrix3.RotateInRadian(rotateAngle, basePoint);
            this.Set(matrix: rotateMatrix * this.Matrix);
            Reset();
        }
        public override void Mirror(Vector2 axisStart, Vector2 axisEnd)
        {
            Matrix3 mirrorMatrix = Matrix3.GetMirror(axisEnd, axisStart);
            this.Set(matrix: mirrorMatrix * this.Matrix);
            Reset();
        }
        public override void Move(Vector2 startPoint, Vector2 endPoint)
        {
            Matrix3 matrix3 = Matrix3.GetMove(startPoint, endPoint);
            this.Set(matrix: matrix3 * this.Matrix);
            Reset();
        }
        public override void Translate(double dx, double dy)
        {
            this.Set(matrix: Matrix3.GetTranslate(dx, dy) * Matrix);
            Reset();
        }

        public override void WriteProperties(Utf8JsonWriter writer, JsonSerializerOptions soptions)
        {
            if (!string.IsNullOrEmpty(Name))
                writer.WriteStringProperty(nameof(Name), this.Name);

            writer.WriteNumberProperty("BlockId", this.Block.Id);
            writer.WriteMatrix3dProperty(nameof(Matrix), this.Matrix);

            if (IsClipOn)
                writer.WriteBoolProperty(nameof(IsClipOn), this.IsClipOn);
            if (!string.IsNullOrEmpty(this.ClipArea))
                writer.WriteStringProperty(nameof(ClipArea), this.ClipArea);

        }
        public override void ReadProperties(ref JsonElement jele)
        {
            if (jele.TryGetProperty(nameof(this.Name), out JsonElement nameProp))
                this.Name = nameProp.GetString();

            this.Block = this.Document.Blocks[jele.ReadIdProperty("BlockId")];
            this.Matrix = jele.ReadMatrix3dProperty(nameof(Matrix));

            if (jele.TryGetProperty(nameof(this.IsClipOn), out JsonElement isClipOnProp))
                this.IsClipOn = isClipOnProp.GetBoolean();
            if (jele.TryGetProperty(nameof(this.ClipArea), out JsonElement clipAreaProp))
                this.ClipArea = clipAreaProp.GetString();
        }

    }
}
